﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Newtonsoft.Json.Serialization;
using Microsoft.Extensions.FileProviders;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using YiSha.Util;
using YiSha.Util.Model;
using YiSha.Admin.Web.Controllers;
using Microsoft.Extensions.Logging;

namespace YiSha.Admin.Web
{
    public class Startup
    {
        public IConfiguration Configuration { get; }
        public IWebHostEnvironment WebHostEnvironment { get; set; }

        public Startup(IConfiguration configuration, IWebHostEnvironment env)
        {
            Configuration = configuration;
            WebHostEnvironment = env;
            GlobalContext.LogWhenStart(env);
            GlobalContext.HostingEnvironment = env;
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        /// <summary>
        /// 该方法由运行时调用。使用此方法将服务添加到容器中。
        /// 这段代码的目的是在ASP.NET Core应用程序启动时，配置各种服务、选项和全局设置，以确保应用程序能够正常运行并提供所需的功能。
        /// </summary>
        /// <param name="services"></param>
        public void ConfigureServices(IServiceCollection services)
        {
            if (WebHostEnvironment.IsDevelopment())
            {
                //如果当前环境是开发环境，就启用Razor页面的运行时编译。
                //这使得在开发过程中，修改页面后可以即时反映在应用程序中，无需重新编译整个项目。
                services.AddRazorPages().AddRazorRuntimeCompilation();
            }
            services.Configure<CookiePolicyOptions>(options =>
            {
                //这个lambda决定给定的请求是否需要用户同意非必要的cookie
                //配置Cookie策略选项，指定是否需要用户同意非必要的cookie，
                //以及设置最小的SameSite策略为None，以确保跨站点请求时会发送cookie。
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            //注册一个全局的HTML编码器，用于在应用程序中对HTML进行编码，以防止跨站点脚本攻击（XSS）
            services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All));
            services.AddControllersWithViews(options =>
            {
                options.Filters.Add<GlobalExceptionFilter>();  // 添加全局异常过滤器
                options.ModelMetadataDetailsProviders.Add(new ModelBindingMetadataProvider()); // 模型元数据提供程序
            }).AddNewtonsoftJson(options =>
            {
                // 返回数据首字母不小写，CamelCasePropertyNamesContractResolver是小写
                // 配置返回JSON数据时的序列化选项
                options.SerializerSettings.ContractResolver = new DefaultContractResolver();
            });

            services.AddMemoryCache(); // 添加内存缓存服务
            services.AddSession(); // 添加会话状态管理服务
            services.AddHttpContextAccessor();// 注册HTTP上下文访问器服务, 以便在应用程序中访问当前HTTP请求的上下文信息。

            services.AddOptions(); // 添加选项配置服务

            //添加选项配置服务以及数据保护服务配置，将数据保护的密钥持久化到文件系统中的指定目录。
            services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(GlobalContext.HostingEnvironment.ContentRootPath + Path.DirectorySeparatorChar + "DataProtection"));

            // 注册Encoding，注册编码提供程序，以支持应用程序中可能使用的各种字符编码。
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

            //从应用程序配置中获取名为"SystemConfig"的配置节，并将其转换为SystemConfig对象。
            //同时，将当前的服务集合和配置对象分别存储在全局上下文中，以便在整个应用程序中使用。
            GlobalContext.SystemConfig = Configuration.GetSection("SystemConfig").Get<SystemConfig>();
            GlobalContext.Services = services;
            GlobalContext.Configuration = Configuration;
        }

        /// <summary>
        /// 此方法由运行时调用。使用此方法配置HTTP请求管道。
        /// 这段代码通过配置不同的中间件和路由规则，实现了静态文件服务、异常处理、会话管理以及请求路由，为 ASP.NET Core 应用程序提供了基本的功能和性能优化。
        /// </summary>
        /// <param name="app"></param>
        /// <param name="env"></param>
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (!string.IsNullOrEmpty(GlobalContext.SystemConfig.VirtualDirectory))
            {
                //这段代码检查全局配置中的虚拟目录路径是否存在，并将其应用于应用程序的请求处理中
                //使用 UsePathBase 中间件可以设置应用程序的根路径，使其模拟在特定路径下运行。
                //例如，如果虚拟目录路径是 /myapp，则所有的请求都会在 /myapp 下进行处理。
                app.UsePathBase(new PathString(GlobalContext.SystemConfig.VirtualDirectory)); // 让 Pathbase 中间件成为第一个处理请求的中间件， 才能正确的模拟虚拟路径
            }
            if (WebHostEnvironment.IsDevelopment())
            {
                //如果应用程序运行在开发环境，会启用开发者异常页面 (UseDeveloperExceptionPage)，方便开发者查看详细的错误信息。
                GlobalContext.SystemConfig.Debug = true;
                app.UseDeveloperExceptionPage();
            }
            else
            {
                //如果是生产环境，则通过 UseExceptionHandler 处理未捕获的异常，并重定向到指定的错误页面 (/Home/Error)。
                app.UseExceptionHandler("/Home/Error");
            }

            //这段代码配置了静态文件服务。首先，通过 UseStaticFiles 中间件提供全局的静态文件服务，
            //同时设置了缓存控制策略 (OnPrepareResponse = GlobalContext.SetCacheControl)。
            //然后，通过另一个 UseStaticFiles 中间件配置了一个特定路径 (/Resource) 的静态文件服务，
            //该服务使用指定的物理文件提供程序 (PhysicalFileProvider) 加载资源文件夹 (resource) 中的内容
            string resource = Path.Combine(env.ContentRootPath, "Resource");
            FileHelper.CreateDirectory(resource);

            app.UseStaticFiles(new StaticFileOptions
            {
                OnPrepareResponse = GlobalContext.SetCacheControl
            });
            app.UseStaticFiles(new StaticFileOptions
            {
                RequestPath = "/Resource",
                FileProvider = new PhysicalFileProvider(resource),
                OnPrepareResponse = GlobalContext.SetCacheControl
            });
            app.UseSession(); //UseSession 启用了 ASP.NET Core 应用程序的会话支持，允许在不同请求之间存储和检索用户特定的状态数据。
            app.UseRouting(); //UseRouting 中间件是 ASP.NET Core 路由系统的一部分，负责确定如何响应传入的 HTTP 请求。
            app.UseEndpoints(endpoints =>
            {
                //"areas": 匹配具有区域参数的请求，并指定默认的控制器、操作和可选的 ID 参数。
                //"default": 匹配没有指定区域参数的请求，并同样指定默认的控制器、操作和可选的 ID 参数。
                endpoints.MapControllerRoute("areas", "{area:exists}/{controller=Home}/{action=Index}/{id?}");
                endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
            });

            //最后一行代码将应用程序服务的提供者 (app.ApplicationServices) 赋值给全局上下文中的 ServiceProvider，
            //使得应用程序中的其他部分可以方便地访问和使用这些服务。
            GlobalContext.ServiceProvider = app.ApplicationServices;
        }
    }
}
